﻿// --------------------------------
// <copyright file="GameSession.cs" company="Faculty of Information Technology, Czech Technical University in Prague">
//     This document is shared by Microsoft Public License (MS-PL)
//     For further details please see http://msdn.microsoft.com/en-us/library/ff648068.aspx or license.txt.
// </copyright>
// <author>Lukáš Molnár</author>

// <email>molnaluk@fit.cvut.cz</email>

// <date>25. 3. 2012 10:55:36</date>

// <summary>
// 
// </summary>
// --------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using FITClub.Episode1.Components;
using LProject.Phone.Net;
using Microsoft.Xna.Framework;

namespace FITClub.Episode1.Controller.Net
{
    public class GameSession
    {
        public enum ConnectionType
        {
            None, Master, Slave
        }

        public const string Level = "Level";
        public const string PlayerCount = "PlayerCount";
        public const string GameType = "GameType";
        public const string LivesNumber = "LivesNumber";
        public const string LevelTime = "LevelTime";
        public const string FragLimit = "FragLimit";

        public Dictionary<string, string> GameInfo { get; private set; }

        public ConnectionType Connection { get; private set; }

        /// <summary>
        /// Is player ready for start game?
        /// </summary>
        public bool Ready
        {
            get { return ready; }
            set
            {
                if (this.Connection != ConnectionType.None)
                {
                    if (value != this.ready)
                    {
                        this.ready = value;
                        SendReadinessPacket();
                    }
                }
            }
        }

        private MulticastClient client;
        private string[] addresses = { "224.64.32.8", "224.64.32.9", "224.64.32.10", "224.64.32.11", "224.64.32.12", "224.64.32.13", "224.64.32.14", "224.64.32.15" };
        private string[] requests;
        private XNATimer timerDownload;
        private string lastSentRquest;
        private int repeated;
        private object SyncRoot;
        private XNATimer timerCheckReadiness;
        private bool ready = false;
        private RawPacket packet;
        private Game game;

        public event Action<string> GameInfoChanged;
        public event Action<GameSession, bool> CreateGameSessionSuccess;
        public event Action<GameSession, bool> GameInfoDownloadSuccess;
        
        public event Action<GameSession> GameSessionReady;
        public event Action<GroupMember> ReadinessChanged;

        public GameSession(MulticastClient client, Game game)
        {
            this.GameInfo = new Dictionary<string, string>();
            this.client = client;
            this.game = game;
            this.client.GroupMembershipChanged += this.client_GroupMembershipChanged;
            requests = (from tp in this.GetType().GetFields() where tp.IsLiteral select tp.GetValue(this).ToString()).ToArray();
            timerDownload = new XNATimer(game) { Interval = TimeSpan.FromMilliseconds(100) };
            timerDownload.Tick += new EventHandler(timerDownload_Tick);

            this.timerCheckReadiness = new XNATimer(game) { Interval = TimeSpan.FromSeconds(15) };
            this.timerCheckReadiness.Tick += new EventHandler(timerCheckReadiness_Tick);
            this.SyncRoot = new object();
            this.packet = new RawPacket(RawPacket.HeaderLength);
        }

        /// <summary>
        /// Requests for presence every 15 seconds
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timerCheckReadiness_Tick(object sender, EventArgs e)
        {
            //this.client.SendPresenceRequest();
        }

        private void timerDownload_Tick(object sender, EventArgs e)
        {
            try
            {
                var notReceived = (from itm in this.requests where this.GameInfo.ContainsKey(itm) == false select itm).First();
                //foreach (var item in notReceived)
                {
                    this.client.SendInfoRequest(notReceived);
                    if (lastSentRquest == notReceived)
                    {
                        repeated++;
                        if (repeated > 30)
                        {
                            WorkItemScheduler.Current.Schedule(new WorkItem1<bool>() { Callback = this.FireDownloadSuccess, Arg1 = false }, 0);
                        }
                    }
                    else
                    {
                        repeated = 0;
                    }
                    lastSentRquest = notReceived;
                    return;
                }
            }
            catch { }
            WorkItemScheduler.Current.Schedule(new WorkItem1<bool>() {Callback = this.FireDownloadSuccess, Arg1 = true }, 0);
        }

        private void FireDownloadSuccess(bool success)
        {
            timerDownload.Stop();
            if (this.GameInfoDownloadSuccess != null)
            {
                this.GameInfoDownloadSuccess(this, success);
            }
        }

        /// <summary>
        /// Tries to create game with defined settings
        /// </summary>
        /// <param name="addressPreset"></param>
        /// <param name="clientName"></param>
        /// <param name="level"></param>
        /// <param name="playerCount"></param>
        /// <param name="gameType"></param>
        /// <param name="livesNumber"></param>
        /// <param name="levelTime"></param>
        /// <param name="fragLimit"></param>
        public void CreateGameSession(int addressPreset, string clientName,
            string level, string playerCount, string gameType, string livesNumber, string levelTime, string fragLimit)
        {
            this.client.ClientName = clientName;
            this.Close();
            this.SetGameInfo(Level, level);
            this.SetGameInfo(PlayerCount, playerCount);
            this.SetGameInfo(GameType, gameType);
            this.SetGameInfo(LivesNumber, livesNumber);
            this.SetGameInfo(LevelTime, levelTime);
            this.SetGameInfo(FragLimit, fragLimit);
            this.client.AddPacketProcessor(this.ProcessInfoRequest);
            this.client.AddPacketProcessor(this.ProcessReadinessPacket);
            this.Connection = ConnectionType.Master;
            this.client.JoinGroup(addresses[addressPreset], MulticastClient.GROUP_PORT);
        }

        /// <summary>
        /// Get address preset.
        /// </summary>
        /// <param name="index"></param>
        /// <returns>Address of a multicast group</returns>
        public string GetAddress(int index)
        {
            return this.addresses[index];
        }

        /// <summary>
        /// Join existing game session as a slave.
        /// </summary>
        /// <param name="address"></param>
        public void JoinGameSession(int address, string clientName)
        {
            if (this.Connection != ConnectionType.Slave)
            {
                this.Close();
            }

            this.client.ClientName = clientName;
            this.client.AddUniquePacketProcessor(this.ProcessInfoResponse);
            this.client.AddUniquePacketProcessor(this.ProcessReadinessPacket);
            this.client.JoinGroup(addresses[address], MulticastClient.GROUP_PORT);
            if (this.client.InClub)
            {
                this.client.SendPresenceRequest();
                //this.timerCheckReadiness.Start();
            }
            this.Connection = ConnectionType.Slave;
        }

        public void SendReadinessPacket()
        {
            if (this.Connection != ConnectionType.None)
            {
                packet.SenderID = this.client.MyID;
                packet.Type = (this.ready) ? PacketType.PlayerReady : PacketType.PlayerNotReady;
                this.client.Send(packet);
            }
        }

        /// <summary>
        /// Closes actual session.
        /// </summary>
        public void Close()
        {
            //lock (client.SyncRoot)
            {
                this.timerDownload.Stop();
                this.timerCheckReadiness.Stop();
                this.client.LeaveGroup();
                this.GameInfo.Clear();
                this.Connection = GameSession.ConnectionType.None;
                Hold();
                this.ready = false;
            }
        }

        /// <summary>
        /// Stops processing packets.
        /// </summary>
        public void Hold()
        {
            this.client.RemovePacketProcessor(this.ProcessInfoResponse);
            this.client.RemovePacketProcessor(this.ProcessInfoRequest);
            this.client.RemovePacketProcessor(this.ProcessReadinessPacket);
        }

        private void client_GroupMembershipChanged(MulticastClient client, bool joined)
        {
            if (this.Connection == ConnectionType.Master && joined)
            {
                bool close = false;
                lock (client.SyncRoot)
                {
                    close = (joined && client.Presence.Count > 1);
                    WorkItemScheduler.Current.Schedule(new WorkItem1<bool>() { Arg1 = !close, Callback = this.FireCreateGameSessionSuccess }, 0);
                }                                
            }
            if (this.Connection == ConnectionType.Slave && joined)
            {
                this.SendInfoRequests();
                this.timerCheckReadiness.Start();
            }
        }

        private void FireCreateGameSessionSuccess(bool success)
        {
            if (success == false)
            {
                this.Close();
            }
            else
            {
                this.timerCheckReadiness.Start();
            }
            if (this.CreateGameSessionSuccess != null)
            {
                this.CreateGameSessionSuccess(this, success);
            }
        }

        /// <summary>
        ///
        /// </summary>
        private void SendInfoRequests()
        {
            this.timerDownload.Start();
        }

        private bool ProcessInfoRequest(IMulticastPacket packet)
        {
            //lock (this.SyncRoot)
            {
                if (packet.Type == PacketType.InfoRequest)
                {
                    if (this.client.MyID == packet.SenderID)
                    {
                        return true;
                    }

                    string request = packet.GetText(RawPacket.HeaderLength, packet.ContentSize);
                    try
                    {
                        string response = this.GameInfo[request];
                        this.client.SendInfoResponse(request, response);
                        Console.WriteLine("{0} -> {1}", request, response);
                        return true;
                    }
                    catch (Exception exc)
                    {
                        Console.WriteLine(exc);
                        return false;
                    }
                }
            }
            return false;
        }

        private bool ProcessInfoResponse(IMulticastPacket packet)
        {
            if (packet.Type == PacketType.InfoResponse)
            {
                if (this.client.MyID == packet.SenderID)
                {
                    return true;
                }
                InfoResponsePacket reponse = new InfoResponsePacket(packet.Data);
                WorkItemScheduler.Current.Schedule(new WorkItem1<InfoResponsePacket>() { Arg1 = reponse, Callback = this.InfoResponse }, 0);
                return true;
            }
            return false;
        }

        private void InfoResponse(InfoResponsePacket reponse)
        {
            string req = reponse.Request;
            this.SetGameInfo(req, reponse.Response);
            if (this.GameInfoChanged != null)
            {
                this.GameInfoChanged(req);
            }
        }

        private bool ProcessReadinessPacket(IMulticastPacket packet)
        {
            bool pready;
            switch (packet.Type)
            {
                case PacketType.PlayerNotReady:
                    pready = false;
                    break;
                case PacketType.PlayerReady:
                    pready = true;
                    break;
                case PacketType.ReadinessRequest:
                    if (this.client.MyID != packet.SenderID)
                    {
                        this.SendReadinessPacket();
                    }
                    return true;
                default: return false;
            }
                WorkItemScheduler.Current.Schedule(new WorkItem2<bool, byte>() { Callback = this.ReadinessCallback, Arg1 = pready, Arg2 = packet.SenderID }, 0);

            return true;
        }

        private void ReadinessCallback(bool pready, byte id)
        {
            GroupMember gm = this.client.GetMemberLocked(id);
            if (gm.ReadyToPlay != pready)
            {
                gm.ReadyToPlay = pready;
                this.ReadinessChanged(gm);
            }
            if (pready)
            {
                int countReady = this.client.Presence.Count((GroupMember ggg) => ggg.ReadyToPlay);
                if (countReady == this.client.Presence.Count && this.GameSessionReady != null) /// everybody is ready
                {
                    this.GameSessionReady(this);
                }
            }
        }

        private void SetGameInfo(string req, string response)
        {
            if (this.GameInfo.ContainsKey(req))
            {
                this.GameInfo[req] = response;
            }
            else
            {
                this.GameInfo.Add(req, response);
            }
        }
    }
}